package org.budo.shiro.session.dao.jedis;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.budo.support.mvcs.Mvcs;
import org.budo.support.slf4j.Slf4j;
import org.slf4j.Logger;

import redis.clients.jedis.Jedis;

/**
 * 使用 redis 存储的 shiro session dao 实现
 * 
 * @author li
 * @version 1 (2015年11月9日 下午8:16:31)
 * @since Java7
 */
public class BudoShiroJedisSessionDao extends AbstractBudoShiroJedisSessionDao {
    private static final Logger log = Slf4j.getLogger();

    /**
     * @see #doReadSession(Serializable)
     */
    public Session readSession(Serializable sessionId) throws UnknownSessionException {
        return super.readSession(sessionId);
    }

    public Collection<Session> getActiveSessions() {
        Jedis jedis = null;
        try {
            jedis = this.getJedis();
            byte[] keyPrefixBytes = (this.getSessionIdPrefix() + "*").getBytes();
            Set<byte[]> keySet = jedis.keys(keyPrefixBytes);
            Set<Session> sessionSet = new HashSet<Session>();
            for (byte[] key : keySet) {
                byte[] valueBytes = jedis.get(key);
                Object deserialize = JedisSessionDaoUtil.deserialize(valueBytes);
                if (null != deserialize) { // 排除空的
                    sessionSet.add((Session) deserialize);
                }
            }
            return sessionSet;
        } catch (Throwable e) {
            log.error("getActiveSessions error, e=" + e, e);
            return new ArrayList<Session>(); // 返回NoSession
        } finally {
            this.returnJedis(jedis);
        }
    }

    protected Session doReadSession(Serializable sessionId) {
        try {
            if (this.isSessionIdIllegal(sessionId)) { // 非法的SessionId
                log.warn("readSession() illegal sessionid " + sessionId + ", return NoSession, ip=" + Mvcs.getRemoteAddr() //
                        + ", Accept=" + Mvcs.getRequestHeader("Accept") //
                        + ", Accept-Encoding=" + Mvcs.getRequestHeader("Accept-Encoding") //
                        + ", Accept-Language=" + Mvcs.getRequestHeader("Accept-Language") //
                        + ", User-Agent=" + Mvcs.getRequestHeader("User-Agent") //
                        + ", Referer=" + Mvcs.getRequestHeader("Referer"));

                return new NoSession(sessionId); // 返回NoSession
            }

            Session session = this.doReadSessionFromRedis(sessionId);
            if (null != session) {
                return session;
            }

            log.warn("doReadSessionFromRedis() sessionId=" + sessionId + ", return null");
            return null; // 返回NULL
        } catch (UnknownSessionException e) {
            log.warn("UnknownSessionException, return NoSession, sessionid=" + sessionId + ", ip=" + Mvcs.getRemoteAddr() //
                    + ", Accept=" + Mvcs.getRequestHeader("Accept") //
                    + ", Accept-Encoding=" + Mvcs.getRequestHeader("Accept-Encoding") //
                    + ", Accept-Language=" + Mvcs.getRequestHeader("Accept-Language") //
                    + ", User-Agent=" + Mvcs.getRequestHeader("User-Agent") //
                    + ", Referer=" + Mvcs.getRequestHeader("Referer"));

            return new NoSession(sessionId); // 返回NoSession
        }
    }

    public Session doReadSessionFromRedis(Serializable sessionId) {
        Jedis jedis = null;
        try {
            byte[] keyBytes = sessionId.toString().getBytes();

            jedis = this.getJedis();
            byte[] valueBytes = jedis.get(keyBytes);

            Object result = JedisSessionDaoUtil.deserialize(valueBytes);
            return (Session) result;
        } catch (Throwable e) {
            log.error("doReadSessionFromRedis error, e=" + e, e);
            this.returnBrokenJedis(jedis);
            return new NoSession(sessionId); // 返回NoSession
        } finally {
            this.returnJedis(jedis);
        }
    }

    protected Serializable doCreate(Session session) {
        String sessionId = this.getSessionIdPrefix() + super.generateSessionId(session);
        super.assignSessionId(session, sessionId);

        this.update(session);

        return session.getId();
    }

    public void update(Session session) throws UnknownSessionException {
        Jedis jedis = null;
        try {
            byte[] keyBytes = session.getId().toString().getBytes();
            byte[] valueBytes = JedisSessionDaoUtil.serialize(session);
            int seconds = (int) (session.getTimeout() / 1000);

            jedis = this.getJedis();
            jedis.set(keyBytes, valueBytes);
            jedis.expire(keyBytes, seconds);
        } catch (Throwable e) {
            log.error("update session error, e=" + e, e);
            this.returnBrokenJedis(jedis);
        } finally {
            this.returnJedis(jedis);
        }
    }

    public void delete(Session session) {
        Jedis jedis = null;
        try {
            String key = session.getId().toString();
            jedis = this.getJedis();
            jedis.del(key);
        } catch (Throwable e) {
            log.error("delete session error, e=" + e, e);
            this.returnBrokenJedis(jedis);
        } finally {
            this.returnJedis(jedis);
        }
    }

    /**
     * SessionId 非法
     */
    private Boolean isSessionIdIllegal(Serializable sessionId) {
        if (null == sessionId) {
            return true;
        }

        String sessionIdString = sessionId.toString();
        if (!sessionIdString.startsWith(this.getSessionIdPrefix())) {
            return true;
        } else if (sessionIdString.length() != (this.getSessionIdPrefix().length() + 32)) { // new UuidSessionIdGenerator().generateId(null).toString().length()=32
            return true;
        } else if (sessionIdString.contains("/") || sessionIdString.contains("\\")) {
            return true;
        } else {
            return false;
        }
    }
}